/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.worlds.object;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import net.worlds.AssetLoader;
import net.worlds.AssetManager;
import net.worlds.WorldsException;
import net.worlds.GameView;
import net.worlds.Main;
import net.worlds.PropChangeHandler;
import net.worlds.TraceResult;
import net.worlds.Util;
import net.worlds.lua.Event;
import org.jbox2d.collision.AABB;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.Vec2D;
import se.krka.kahlua.vm.JavaFunction;
import se.krka.kahlua.vm.LuaCallFrame;


public abstract class AbstractCharacter extends GameObject implements Steppable, Prop{
    private boolean visible = true;
    public double mouseAngle;
    private int limbRotation = 0;
    private int limbStep = 1;
    private int limbMovement = 0;
    private VolatileImage character;

    private int headIndex = 0;
    private boolean useAdvancedSkin = false;
    private long stopMovingTime = 0;
    
    private BufferedImage skin;
    private VolatileImage head0;
    private VolatileImage head1;
    private VolatileImage head2;
    private VolatileImage torso;
    private VolatileImage leftArm;
    private VolatileImage rightArm;
    private VolatileImage leftLeg;
    private VolatileImage rightLeg;
    
    private static final Vec2D headPos = new Vec2D(88, 58);
    private static final Vec2D armPos = new Vec2D(94, 84);
    private static final Vec2D armCenter = new Vec2D(101, 84);
    protected Vec2D armAbsWorldCenter = new Vec2D(0, 0);
    protected Vec2D armAbsCenter = new Vec2D(0, 0);
    private static final Vec2D torsoPos = new Vec2D(90, 82);
    private static final Vec2D legPos = new Vec2D(92, 110);
    private static final Vec2D size = new Vec2D(2.5f, 10.5f);

    private static final Rectangle headR = new Rectangle((int) headPos.x, (int) headPos.y, 24, 24);
    private static final Rectangle torsoR = new Rectangle( (int) torsoPos.x, (int) torsoPos.y, 20, 40);
    private static final Rectangle arm = new Rectangle((int) armPos.x, (int) armPos.y, 14, 30);
    private static final Rectangle leg = new Rectangle((int) legPos.x, (int) legPos.y, 16, 32);

    private java.awt.Color headColor = java.awt.Color.gray;
    private java.awt.Color torsoColor = java.awt.Color.gray;
    private java.awt.Color leftArmColor = java.awt.Color.gray;
    private java.awt.Color rightArmColor = java.awt.Color.gray;
    private java.awt.Color leftLegColor = java.awt.Color.gray;
    private java.awt.Color rightLegColor = java.awt.Color.gray;
    private long lastSendTime;
    public boolean isDead = false;
    public AbstractCharacter(String type) throws WorldsException
    {
        super(type);
        addPropChangeHandler("Health", new PropChangeHandler(false)
        {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                if((Integer) value <= 0 && !Main.loading)
                    kill();
                if(oldValue != null && (Integer) value < (Integer) oldValue && isPlayer() && Main.getView() instanceof GameView)
                {
                    ((GameView)Main.getView()).hurt();
                }
            }

        });
        addPropChangeHandler("Visible", new PropChangeHandler()
        {
            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                visible = (Boolean) value;
            }
        });
        addPropChangeHandler("HeadColor", new PropChangeHandler()
        {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                headColor = ((Color)value);
            }
            
        });
        addPropChangeHandler("TorsoColor", new PropChangeHandler()
        {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                torsoColor = ((Color)value);
            }

        });
        addPropChangeHandler("LeftArmColor", new PropChangeHandler()
        {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                leftArmColor = ((Color)value);
            }

        });
        addPropChangeHandler("RightArmColor", new PropChangeHandler()
        {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                rightArmColor = ((Color)value);
            }

        });
        addPropChangeHandler("LeftLegColor", new PropChangeHandler()
        {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                leftLegColor = ((Color)value);
            }

        });
        addPropChangeHandler("RightLegColor", new PropChangeHandler()
        {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                rightLegColor = ((Color)value);
            }

        });
        addPropChangeHandler("Skin", new PropChangeHandler()
        {

            @Override
            public void handle(Object oldValue, Object value) throws WorldsException {
                final String path = value.toString();
                if(Main.noView)
                    return;
                Runnable r = new Runnable()
                {

                    public void run() {
                        try
                        {
                            if(path.equals(""))
                            {
                                skin = null;
                                head1 = null;
                                torso = null;
                                leftArm = null;
                                rightArm = null;
                                leftLeg = null;
                                rightLeg = null;
                                return;
                            }
                            skin = AssetManager.loadImage(path, path, false);
                            if(skin.getWidth() != 110 || skin.getHeight() != 64)
                                throw new RuntimeException("Skin image must be 110x64");
                            head0 = Util.toVolatileImage(skin.getSubimage(0, 0, 24, 24));
                            int[] px = new int[4];
                            skin.getData().getPixel(30, 17, px);
                            if(!((px[0] == 0 && px[1] == 0 && px[2] == 0) || px[3] == 0))
                            {
                                useAdvancedSkin = true;
                                head1 = Util.toVolatileImage(skin.getSubimage(24, 0, 24, 24));
                                head2 = Util.toVolatileImage(skin.getSubimage(48, 0, 24, 24));
                            }
                            else
                                useAdvancedSkin = false;
                            torso = Util.toVolatileImage(skin.getSubimage(0, 24, 20, 40));
                            leftArm = Util.toVolatileImage(skin.getSubimage(50, 24, 14, 30));
                            rightArm = Util.toVolatileImage(skin.getSubimage(64, 24, 14, 30));
                            leftLeg = Util.toVolatileImage(skin.getSubimage(78, 24, 15, 32));
                            rightLeg = Util.toVolatileImage(skin.getSubimage(93, 24, 15, 32));
                            //System.err.println((head.getTransparency() == Transparency.BITMASK)+" "+(skin.getTransparency() == Transparency.BITMASK));
                        } catch (Exception ex) {
                            ex.printStackTrace();
                            throw new RuntimeException("Invalid texture URL");
                        }
                    }
                };
                AssetLoader.getInstance().addItem(r);
            }

        });
        //x_;
        addFunction("kill", new JavaFunction()
        {

            public int call(LuaCallFrame callFrame, int nArguments) {
                Main.runInMainThread(new Runnable() {

                    public void run() {
                        try
                        {
                            kill();
                        }
                        catch(Exception ex)
                        {
                            throw new RuntimeException(ex);
                        }
                    }
                });
                return 0;
            }

        });
        addFunction("getTrace", new JavaFunction()
        {
            public int call(LuaCallFrame callFrame, int nArguments) {
                Vec2D pos = armAbsWorldCenter;
                double angle;
                try {
                    angle = -getArmAngle() - Math.PI/2;
                    Vec2D unit = new Vec2D(angle);
                    TraceResult r = world.getTrace(pos, unit, getPart());
                    if(r == null)
                        callFrame.pushNil();
                    else
                        callFrame.push(r);
                    return 1;
                } catch (WorldsException ex) {
                    throw new RuntimeException(ex);
                }
            }
        });
        addEvent("Died", new Event(), "none");
        if(!Main.noView)
            character = Util.getGraphicsConfiguration().createCompatibleVolatileImage(200, 200, Transparency.TRANSLUCENT);
    }
    private Part getPart() throws WorldsException
    {
        if(hasChild("Body"))
            return (Part) getChild("Body");
        else
            return null;
    }
    @Override
    protected void finishChangeWorld()
    {
        try {
            if (isPlayer()) {
                Player p = Main.getPlayer();
                p.setParent(world.getService("Players"));
                Main.setLocalPlayer(null);
                Main.setLocalPlayer(p);
            }
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
        super.finishChangeWorld();
    }
    @Override
    protected void beforeChangeWorld(net.worlds.World newWorld)
    {
        try {
            if (isPlayer()) {
                world.localPlayer = null;
            }
        } catch (WorldsException ex) {
        }
        super.beforeChangeWorld(newWorld);
    }
    @Override
    public void step() throws WorldsException
    {
        boolean isPlayer = isPlayer();
        if(hasChild("Body") && getChild("Body") instanceof Part)
        {
            Part bodyPart = (Part) getChild("Body");
            if(bodyPart.getOldPosition().length() > 0 && !isDead)
            {
                Vec2D diff = bodyPart.getPosition().sub(bodyPart.getOldPosition());
                if(isPlayer() && getPositionDifference().x == 0)
                    diff.x = 0;
                setLimbMovement((int) Math.round(diff.x / 0.1));
                diff = getPositionDifference();
                if(diff.x > 0)
                {
                    headIndex = 2;
                    stopMovingTime = 0;
                }
                else if(diff.x < 0)
                {
                    headIndex = 1;
                    stopMovingTime = 0;
                }
                else if(diff.x == 0)
                {
                    if(stopMovingTime > 0 && System.currentTimeMillis() - stopMovingTime > 500)
                    {
                        headIndex = 0;
                    }
                    if(stopMovingTime == 0)
                        stopMovingTime = System.currentTimeMillis();
                }
            }
            if(Main.getGame().type == 0)
            {
                AABB queryAABB = new AABB();
                queryAABB.lowerBound.x = (float) (bodyPart.getPosition().x - 1.25) - 0.25f;
                queryAABB.lowerBound.y = (float) (bodyPart.getPosition().y - 5.25) - 0.25f;
                queryAABB.upperBound.x = (float) (bodyPart.getPosition().x + 1.25) + 0.25f;
                queryAABB.upperBound.y = (float) (bodyPart.getPosition().y + 5.25) + 0.25f;
                Shape[] r = world.query(queryAABB, 64);
                for(Shape s : r)
                {
                    if(s.getUserData() != null && s.getUserData() != bodyPart && world.shouldCollide(s, bodyPart.getShape()))
                    {
                        final Part finalCharacter = bodyPart;
                        if(s.getUserData() instanceof Box)
                        {
                            final Part finalBox = (Box) s.getUserData();
                            final Vec2D highest = new Vec2D(((PolygonShape)finalBox.getShape()).getHighestPoint((float) finalCharacter.getPosition().x, (float) (size.x/2 + 0.25f)));
                            final boolean doIt = highest.y > finalCharacter.getPosition().y - size.y/2 && highest.y < finalCharacter.getPosition().y - size.y/2 + 2.5 && Math.abs(finalCharacter.getVelocity().x) > 1 && finalCharacter.getVelocity().y < 5 && !(isPlayer && Main.getLocalPlayer().isJumping);
                            final Vec2D oldPos = finalCharacter.getPosition();
                            if(doIt)
                            {
                                float add;
                                if(finalCharacter.getBody().getLinearVelocity().x > 0)
                                    add = 0.2f;
                                else
                                    add = -0.2f;
                                add = 0;
                                float d = highest.y - finalCharacter.getPosition().y + size.y/2;
                                if(d < 0.01)
                                    continue;
                                float addY = 0.125f;
                                finalCharacter.stepFrame = world.frame;
                                finalCharacter.getBody().setXForm(new Vec2D(finalCharacter.getBody().getPosition().x + add, finalCharacter.getBody().getPosition().y + d + addY), finalCharacter.getBody().getAngle());
                            }
                        }
                        if(isPlayer && (s.testPoint(s.getBody().getXForm(), finalCharacter.getPosition().add(new Vec2D(0, -5.5f))) || s.testPoint(s.getBody().getXForm(), finalCharacter.getPosition().add(new Vec2D(-1f, -5.5f))) || s.testPoint(s.getBody().getXForm(), finalCharacter.getPosition().add(new Vec2D(1f, -5.5f)))) && world.getContactFilter().shouldCollide(s, finalCharacter.getShape()))
                            Main.getLocalPlayer().isJumping = false;
                    }
                }
            }
        }
        limbRotation += limbStep * limbMovement;
        if(limbRotation > 30)
        {
            limbStep = -limbStep;
            limbRotation = 30;
        }
        if(limbRotation < -30)
        {
            limbStep = -limbStep;
            limbRotation = -30;
        }
        if(Main.isClient() && isPlayer && System.currentTimeMillis() - lastSendTime > 100)
        {
            try {
                if (getPart() == null)
                    return;
                Main.getClient().sendPosition(getPart());
                Main.getClient().sendArmRotation(mouseAngle - Math.PI/2);
                lastSendTime = System.currentTimeMillis();
            } catch (WorldsException ex) {
                ex.printStackTrace();
            }
        }
    }
    public void setLimbMovement(int x)
    {
        limbMovement = x;
    }
    public void kill() throws WorldsException
    {
        if(Main.isClient())
            return;
        if(hasChild("Body"))
        {
            if(getPart().hasChild("Controller"))
                getPart().getChild("Controller").remove();
            if(getPart().hasChild("FixedRotation"))
                getPart().getChild("FixedRotation").remove();
        }
        if(world.getService("Players").hasChild(toString()))
            world.getService("Players").getChild(toString()).getEvent("Died").fire(Event.NULL);
        getEvent("Died").fire(Event.NULL);
    }
    public void onDied() throws WorldsException
    {
        if(isDead)
            return;
        isDead = true;
        if(hasChild("Body"))
        {
            if(getPart().hasChild("Controller"))
                getPart().getChild("Controller").remove();
            if(getPart().hasChild("FixedRotation"))
                getPart().getChild("FixedRotation").remove();
            getPart().isRemoteCharacter = false;
            getPart().getBody().setLinearVelocity(new Vec2D(0, 0));
        }
        if(isPlayer())
        {
            Main.getLocalPlayer().deselectTool();
            Main.getLocalPlayer().enableArmRotation = false;
        }
        setLimbMovement(0);
    }
    public void fixedRotationRemoved()
    {
        double r = (Math.random() * Math.PI) - (Math.PI/2);
        if(r < 0)
            r -= Math.PI/2;
        else
            r += Math.PI/2;
        try {
            if (getPart() != null) {
                getPart().getBody().setAngularVelocity((float) r);
            }
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
    }
    @Override
    public void draw(Graphics g) throws WorldsException
    {
        if(!visible)
            return;
        Graphics2D g2 = (Graphics2D) g;
        updateImage();
        if(!hasChild("Body"))
            return;
        Vec2D pos = getPart().getDrawPosition().worldToScreen(Main.getView());
        int w = (int) (200*(Main.getView().scaleFactor/8));
        int h = (int) (200*(Main.getView().scaleFactor/8));
        int imgX = (int) (pos.x - w/2);
        int imgY = (int) (pos.y - h/2);
        AffineTransform orig = ((Graphics2D)g).getTransform();
        AffineTransform rotate = AffineTransform.getRotateInstance(-getPart().getBody().getAngle() - getPart().rotationDiff, pos.x, pos.y);
        g2.transform(rotate);
        g2.drawImage(character, imgX, imgY, w, h, null);
        g2.setTransform(orig);
        getPart().drawLabel(g, pos);
    }
    public void updateImage() throws WorldsException
    {
        if(!hasChild("Body"))
            return;
        Graphics2D g2 = (Graphics2D) character.getGraphics();
        Composite c = g2.getComposite();
        g2.setComposite(AlphaComposite.Src);
        g2.setColor(Util.transparent);
        g2.fillRect(0, 0, 200, 200);
        g2.setComposite(c);
        //g2.setComposite(AlphaComposite.SrcOver);
        AffineTransform orig = g2.getTransform(); //localWorldPosToScreenPos(getPart().getPosition())
        double legRotation;
        double armRotation;
        if(this instanceof Character && ((Character)this).seat != null)
        {
            legRotation = 0;
            armRotation = 0;
        }
        else
        {
            legRotation = limbRotation;
            armRotation = limbRotation;
        }
        g2.setColor(leftLegColor);
        AffineTransform lLegRotate = AffineTransform.getRotateInstance(Math.toRadians(legRotation), legPos.x + 8, legPos.y + 1);
        g2.setTransform(lLegRotate);
        if(leftLeg != null)
            g2.drawImage(leftLeg, (int) legPos.x, (int) legPos.y, null);
        else
            g2.fill(leg);
        g2.setColor(rightLegColor);
        AffineTransform rLegRotate = AffineTransform.getRotateInstance(Math.toRadians(-legRotation), legPos.x + 8, legPos.y + 1);
        g2.setTransform(rLegRotate);
        if(rightLeg != null)
            g2.drawImage(rightLeg, (int) legPos.x, (int) legPos.y, null);
        else
            g2.fill(leg);
        g2.setColor(leftArmColor);
        AffineTransform lArmRotate = AffineTransform.getRotateInstance(Math.toRadians(armRotation), armCenter.x, armCenter.y);
        g2.setTransform(lArmRotate);
        VolatileImage bottomArm;
        if(headIndex == 1)
            bottomArm = rightArm;
        else
            bottomArm = leftArm;
        if(bottomArm != null)
            g2.drawImage(bottomArm, (int) armPos.x, (int) armPos.y, null);
        else
            g2.fill(arm);
        g2.setTransform(orig);
        g2.setColor(torsoColor);
        if(torso != null)
            g2.drawImage(torso, (int) torsoPos.x, (int) torsoPos.y, null);
        else
            g2.fill(torsoR);
        g2.setColor(headColor);
        VolatileImage head;
        if(useAdvancedSkin)
            switch(headIndex)
            {
                case 0:
                    head = head0;
                break;
                case 1:
                    head = head1;
                break;
                case 2:
                    head = head2;
                break;
                default:
                    head = head0;
                break;
            }
        else
            head = head0;
        if(head != null)
            g2.drawImage(head, (int) headPos.x, (int) headPos.y, null);
        else
            g2.fill(headR);
        g2.setColor(rightArmColor);
        AffineTransform rArmRotate;
        armAbsWorldCenter = getPart().getPosition().add(new Vec2D(0, 1.9f));
        armAbsCenter = armAbsWorldCenter.worldToScreen(Main.getView());
        double armAngle = getArmAngle();
        if(!armRotationEnabled())
        {
            rArmRotate = AffineTransform.getRotateInstance(Math.toRadians(-armRotation), armCenter.x, armCenter.y);
            g2.setTransform(rArmRotate);
        }
        else
        {
            rArmRotate = AffineTransform.getRotateInstance(armAngle, armCenter.x, armCenter.y);
            g2.setTransform(rArmRotate);
        }
        VolatileImage topArm;
        if(headIndex == 1)
            topArm = leftArm;
        else
            topArm = rightArm;
        if(topArm != null)
            g2.drawImage(topArm, (int) armPos.x, (int) armPos.y, 14, 30, null);
        else
            g2.fill(arm);
        BufferedImage img = getToolTexture();
        if(img != null)
        {
            Vec2D vec = getToolOffset();
            /*if(Main.getLocalPlayer() == null)
                return;
            Tool t = Main.getLocalPlayer().getSelectedTool();*/
            if(img != null && vec != null)
            {
                AffineTransform toolRotate = rArmRotate = AffineTransform.getRotateInstance(armAngle + Math.PI/2, armCenter.x, armCenter.y);
                int x = (int) (vec.x * 8);
                int y = (int) (vec.y * 8);
                Vec2D s = getToolSize();
                int sx = (int) (s.x * 8);
                int sy = (int) (s.y * 8);
                toolRotate.translate(x, 0);
                g2.setComposite(AlphaComposite.SrcOver);
                if(armAngle < 0 && armAngle > -Math.PI)
                {
                    toolRotate.translate(0, -y);
                    g2.setTransform(toolRotate);
                    g2.drawImage(img, (int) armPos.x + 20, (int) armPos.y, (int) armPos.x + sx + 20, (int) armPos.y + sy, 0, 0, sx, sy, null);

                }
                else
                {
                    toolRotate.translate(0, y - sy);
                    g2.setTransform(toolRotate);
                    g2.drawImage(img, (int) armPos.x + 20, (int) armPos.y, (int) armPos.x + sx + 20, (int) armPos.y + sy, 0, sy, sx, 0, null);
                }
            }
        }
        g2.setTransform(orig);
    }
    protected abstract Vec2D getPositionDifference();
    protected abstract double getArmAngle() throws WorldsException;
    protected abstract BufferedImage getToolTexture() throws WorldsException;
    protected abstract Vec2D getToolSize() throws WorldsException;
    protected abstract Vec2D getToolOffset() throws WorldsException;
    protected abstract boolean armRotationEnabled() throws WorldsException;
    public abstract boolean isPlayer() throws WorldsException;
}
